/**
 * FiltersFeature is a grid {@link Ext.grid.feature.Feature feature} that allows for a slightly more
 * robust representation of filtering than what is provided by the default store.
 *
 * Filtering is adjusted by the user using the grid's column header menu (this menu can be
 * disabled through configuration). Through this menu users can configure, enable, and
 * disable filters for each column.
 *
 * #Features#
 *
 * ##Filtering implementations:##
 *
 * Default filtering for Strings, Numeric Ranges, Date Ranges, Lists (which can be backed by a
 * {@link Ext.data.Store}), and Boolean. Additional custom filter types and menus are easily
 * created by extending {@link Ext.ux.grid.filter.Filter}.
 *
 * ##Graphical Indicators:##
 *
 * Columns that are filtered have {@link #filterCls a configurable css class} applied to the column headers.
 *
 * ##Automatic Reconfiguration:##
 *
 * Filters automatically reconfigure when the grid 'reconfigure' event fires.
 *
 * ##Stateful:##
 *
 * Filter information will be persisted across page loads by specifying a `stateId`
 * in the Grid configuration.
 *
 * The filter collection binds to the {@link Ext.grid.Panel#beforestaterestore beforestaterestore}
 * and {@link Ext.grid.Panel#beforestatesave beforestatesave} events in order to be stateful.
 *
 * ##GridPanel Changes:##
 *
 * - A `filters` property is added to the GridPanel using this feature.
 * - A `filterupdate` event is added to the GridPanel and is fired upon onStateChange completion.
 *
 * ##Server side code examples:##
 *
 * - [PHP](http://www.vinylfox.com/extjs/grid-filter-php-backend-code.php) - (Thanks VinylFox)
 * - [Ruby on Rails](http://extjs.com/forum/showthread.php?p=77326#post77326) - (Thanks Zyclops)
 * - [Ruby on Rails](http://extjs.com/forum/showthread.php?p=176596#post176596) - (Thanks Rotomaul)
 *
 * #Example usage:#
 *
 *     var store = Ext.create('Ext.data.Store', {
 *         pageSize: 15
 *         ...
 *     });
 *
 *     var filtersCfg = {
 *         ftype: 'filters',
 *         autoReload: false, //don't reload automatically
 *         local: true, //only filter locally
 *         // filters may be configured through the plugin,
 *         // or in the column definition within the headers configuration
 *         filters: [{
 *             type: 'numeric',
 *             dataIndex: 'id'
 *         }, {
 *             type: 'string',
 *             dataIndex: 'name'
 *         }, {
 *             type: 'numeric',
 *             dataIndex: 'price'
 *         }, {
 *             type: 'date',
 *             dataIndex: 'dateAdded'
 *         }, {
 *             type: 'list',
 *             dataIndex: 'size',
 *             options: ['extra small', 'small', 'medium', 'large', 'extra large'],
 *             phpMode: true
 *         }, {
 *             type: 'boolean',
 *             dataIndex: 'visible'
 *         }]
 *     };
 *
 *     var grid = Ext.create('Ext.grid.Panel', {
 *          store: store,
 *          columns: ...,
 *          features: [filtersCfg],
 *          height: 400,
 *          width: 700,
 *          bbar: Ext.create('Ext.PagingToolbar', {
 *              store: store
 *          })
 *     });
 *
 *     // a filters property is added to the GridPanel
 *     grid.filters
 */
Ext.define('Ext.ux.grid.FiltersFeature', 
{
    extend : 'Ext.grid.feature.Feature',
    alias : 'feature.filters',
    uses : ['Ext.ux.grid.menu.ListMenu', 'Ext.ux.grid.menu.RangeMenu', 'Ext.ux.grid.filter.BooleanFilter', 'Ext.ux.grid.filter.DateFilter', 'Ext.ux.grid.filter.ListFilter', 'Ext.ux.grid.filter.NumericFilter', 'Ext.ux.grid.filter.StringFilter'],
    
    /**
     * @cfg {Boolean} autoReload
     * Defaults to true, reloading the datasource when a filter change happens.
     * Set this to false to prevent the datastore from being reloaded if there
     * are changes to the filters.  See <code>{@link #updateBuffer}</code>.
     */
    autoReload : true,
    /**
     * @cfg {Boolean} encode
     * Specify true for {@link #buildQuery} to use Ext.util.JSON.encode to
     * encode the filter query parameter sent with a remote request.
     * Defaults to false.
     */
    
    /**
     * @cfg {Array} filters
     * An Array of filters config objects. Refer to each filter type class for
     * configuration details specific to each filter type. Filters for Strings,
     * Numeric Ranges, Date Ranges, Lists, and Boolean are the standard filters
     * available.
     */
    
    /**
     * @cfg {String} filterCls
     * The css class to be applied to column headers with active filters.
     * Defaults to <tt>'ux-filterd-column'</tt>.
     */
    filterCls : 'ux-filtered-column',
    /**
     * @cfg {Boolean} local
     * <tt>true</tt> to use Ext.data.Store filter functions (local filtering)
     * instead of the default (<tt>false</tt>) server side filtering.
     */
    local : false,
    /**
     * @cfg {String} menuFilterText
     * defaults to <tt>'Filters'</tt>.
     */
    menuFilterText : 'Filters',
    /**
     * @cfg {String} paramPrefix
     * The url parameter prefix for the filters.
     * Defaults to <tt>'filter'</tt>.
     */
    paramPrefix : 'filter',
    /**
     * @cfg {Boolean} showMenu
     * Defaults to true, including a filter submenu in the default header menu.
     */
    showMenu : true,
    /**
     * @cfg {String} stateId
     * Name of the value to be used to store state information.
     */
    stateId : undefined,
    /**
     * @cfg {Number} updateBuffer
     * Number of milliseconds to defer store updates since the last filter change.
     */
    updateBuffer : 500,
    
    // doesn't handle grid body events
    hasFeatureEvent : false,
    
    
    /** @private */
    constructor : function(config){
        var me = this;
        
        config = config || {};
        Ext.apply(me, config);
        
        me.deferredUpdate = Ext.create('Ext.util.DelayedTask', me.reload, me);
        
        // Init filters
        me.filters = me.createFiltersCollection();
        me.filterConfigs = config.filters;
    },
    
    attachEvents : function(){
        var me = this, view = me.view, headerCt = view.headerCt, grid = me.getGridPanel();
        
        me.bindStore(view.getStore(), true);
        
        // Listen for header menu being created
        headerCt.on('menucreate', me.onMenuCreate, me);
        
        view.on('refresh', me.onRefresh, me);
        grid.on({
            scope : me,
            beforestaterestore : me.applyState,
            beforestatesave : me.saveState,
            beforedestroy : me.destroy
        });
        
        // Add event and filters shortcut on grid panel
        grid.filters = me;
        grid.addEvents('filterupdate');
    },
    
    createFiltersCollection : function(){
        return Ext.create('Ext.util.MixedCollection', false, function(o){
            return o ? o.dataIndex : null;
        });
    },
    
    /**
     * @private Create the Filter objects for the current configuration, destroying any existing ones first.
     */
    createFilters : function(){
        var me = this, hadFilters = me.filters.getCount(), grid = me.getGridPanel(), filters = me.createFiltersCollection(), model = grid.store.model, fields = model.prototype.fields, field,
        filter,
        state;
        
        if(hadFilters){
            state = {};
            me.saveState(null, state);
        }
        
        function add(dataIndex, config, filterable){
            if(dataIndex && (filterable || config)){
                field = fields.get(dataIndex);
                filter = 
                {
                    dataIndex : dataIndex,
                    type : (field && field.type && field.type.type) || 'auto'
                };
                
                if(Ext.isObject(config)){
                    Ext.apply(filter, config);
                }
                
                filters.replace(filter);
            }
        }
        
        // We start with filters from our config
        Ext.Array.each(me.filterConfigs, function(filterConfig){
            add(filterConfig.dataIndex, filterConfig);
        });
        
        // Then we merge on filters from the columns in the grid. The columns' filters take precedence.
        Ext.Array.each(grid.columns, function(column){
            if(column.filterable === false){
                filters.removeAtKey(column.dataIndex);
            }
            else{
                add(column.dataIndex, column.filter, column.filterable);
            }
        });
        
        
        me.removeAll();
        if(filters.items){
            me.initializeFilters(filters.items);
        }
        
        if(hadFilters){
            me.applyState(null, state);
        }
    },
    
    /**
     * @private
     */
    initializeFilters : function(filters){
        var me = this, filtersLength = filters.length, i,
        filter,
        FilterClass;
        
        for(i = 0; i < filtersLength; i ++ ){
            filter = filters[i];
            if(filter){
                FilterClass = me.getFilterClass(filter.type);
                filter = filter.menu ? filter : new FilterClass(filter);
                me.filters.add(filter);
                Ext.util.Observable.capture(filter, this.onStateChange, this);
            }
        }
    },
    
    /**
     * @private Handle creation of the grid's header menu. Initializes the filters and listens
     * for the menu being shown.
     */
    onMenuCreate : function(headerCt, menu){
        var me = this;
        me.createFilters();
        menu.on('beforeshow', me.onMenuBeforeShow, me);
    },
    
    /**
     * @private Handle showing of the grid's header menu. Sets up the filter item and menu
     * appropriate for the target column.
     */
    onMenuBeforeShow : function(menu){
        var me = this, menuItem,
        filter;
        
        if(me.showMenu){
            menuItem = me.menuItem;
            if( ! menuItem || menuItem.isDestroyed){
                me.createMenuItem(menu);
                menuItem = me.menuItem;
            }
            
            filter = me.getMenuFilter();
            
            if(filter){
                menuItem.setMenu(filter.menu, false);
                menuItem.setChecked(filter.active);
                // disable the menu if filter.disabled explicitly set to true
                menuItem.setDisabled(filter.disabled === true);
            }
            menuItem.setVisible( !! filter);
            this.sep.setVisible( !! filter);
        }
    },
    
    
    createMenuItem : function(menu){
        var me = this;
        me.sep = menu.add('-');
        me.menuItem = menu.add({
            checked : false,
            itemId : 'filters',
            text : me.menuFilterText,
            listeners : 
            {
                scope : me,
                checkchange : me.onCheckChange,
                beforecheckchange : me.onBeforeCheck
            }
        });
    },
    
    getGridPanel : function(){
        return this.view.up('gridpanel');
    },
    
    /**
     * @private
     * Handler for the grid's beforestaterestore event (fires before the state of the
     * grid is restored).
     * @param {Object} grid The grid object
     * @param {Object} state The hash of state values returned from the StateProvider.
     */
    applyState : function(grid, state){
        var me = this, key,
        filter;
        me.applyingState = true;
        me.clearFilters();
        if(state.filters){
            for(key in state.filters){
                if(state.filters.hasOwnProperty(key)){
                    filter = me.filters.get(key);
                    if(filter){
                        filter.setValue(state.filters[key]);
                        filter.setActive(true);
                    }
                }
            }
        }
        me.deferredUpdate.cancel();
        if(me.local){
            me.reload();
        }
        delete me.applyingState;
        delete state.filters;
    },
    
    /**
     * Saves the state of all active filters
     * @param {Object} grid
     * @param {Object} state
     * @return {Boolean}
     */
    saveState : function(grid, state){
        var filters = {};
        this.filters.each(function(filter){
            if(filter.active){
                filters[filter.dataIndex] = filter.getValue();
            }
        });
        return(state.filters = filters);
    },
    
    /**
     * @private
     * Handler called by the grid 'beforedestroy' event
     */
    destroy : function(){
        var me = this;
        Ext.destroyMembers(me, 'menuItem', 'sep');
        me.removeAll();
        me.clearListeners();
    },
    
    /**
     * Remove all filters, permanently destroying them.
     */
    removeAll : function(){
        if(this.filters){
            Ext.destroy.apply(Ext, this.filters.items);
            // remove all items from the collection
            this.filters.clear();
        }
    },
    
    
    /**
     * Changes the data store bound to this view and refreshes it.
     * @param {Ext.data.Store} store The store to bind to this view
     */
    bindStore : function(store){
        var me = this;
        
        // Unbind from the old Store
        if(me.store && me.storeListeners){
            me.store.un(me.storeListeners);
        }
        
        // Set up correct listeners
        if(store){
            me.storeListeners = 
            {
                scope : me
            };
            if(me.local){
                me.storeListeners.load = me.onLoad;
            }
            else{
                me.storeListeners['before' + (store.buffered ? 'prefetch' : 'load')] = me.onBeforeLoad;
            }
            store.on(me.storeListeners);
        }
        else{
            delete me.storeListeners;
        }
        me.store = store;
    },
    
    /**
     * @private
     * Get the filter menu from the filters MixedCollection based on the clicked header
     */
    getMenuFilter : function(){
        var header = this.view.headerCt.getMenu().activeHeader;
        return header ? this.filters.get(header.dataIndex) : null;
    },
    
    /** @private */
    onCheckChange : function(item, value){
        this.getMenuFilter().setActive(value);
    },
    
    /** @private */
    onBeforeCheck : function(check, value){
        return ! value || this.getMenuFilter().isActivatable();
    },
    
    /**
     * @private
     * Handler for all events on filters.
     * @param {String} event Event name
     * @param {Object} filter Standard signature of the event before the event is fired
     */
    onStateChange : function(event, filter){
        if(event !== 'serialize'){
            var me = this, grid = me.getGridPanel();
            
            if(filter == me.getMenuFilter()){
                me.menuItem.setChecked(filter.active, false);
            }
            
            if((me.autoReload || me.local) && !me.applyingState){
                me.deferredUpdate.delay(me.updateBuffer);
            }
            me.updateColumnHeadings();
            
            if( ! me.applyingState){
                grid.saveState();
            }
            grid.fireEvent('filterupdate', me, filter);
        }
    },
    
    /**
     * @private
     * Handler for store's beforeload event when configured for remote filtering
     * @param {Object} store
     * @param {Object} options
     */
    onBeforeLoad : function(store, options){
        options.params = options.params || {};
        this.cleanParams(options.params);
        var params = this.buildQuery(this.getFilterData());
        Ext.apply(options.params, params);
    },
    
    /**
     * @private
     * Handler for store's load event when configured for local filtering
     * @param {Object} store
     */
    onLoad : function(store){
        store.filterBy(this.getRecordFilter());
    },
    
    /**
     * @private
     * Handler called when the grid's view is refreshed
     */
    onRefresh : function(){
        this.updateColumnHeadings();
    },
    
    /**
     * Update the styles for the header row based on the active filters
     */
    updateColumnHeadings : function(){
        var me = this, headerCt = me.view.headerCt;
        if(headerCt){
            headerCt.items.each(function(header){
                var filter = me.getFilter(header.dataIndex);
                header[filter && filter.active ? 'addCls' : 'removeCls'](me.filterCls);
            });
        }
    },
    
    /** @private */
    reload : function(){
        var me = this, store = me.view.getStore();
        
        if(me.local){
            store.clearFilter(true);
            store.filterBy(me.getRecordFilter());
            store.sort();
        }
        else{
            me.deferredUpdate.cancel();
            if(store.buffered){
                store.pageMap.clear();
            }
            store.loadPage(1);
        }
    },
    
    /**
     * Method factory that generates a record validator for the filters active at the time
     * of invokation.
     * @private
     */
    getRecordFilter : function(){
        var f = [], len,
        i;
        this.filters.each(function(filter){
            if(filter.active){
                f.push(filter);
            }
        });
        
        len = f.length;
        return function(record){
            for(i = 0; i < len; i ++ ){
                if( ! f[i].validateRecord(record)){
                    return false;
                }
            }
            return true;
        };
    },
    
    /**
     * Adds a filter to the collection and observes it for state change.
     * @param {Object/Ext.ux.grid.filter.Filter} config A filter configuration or a filter object.
     * @return {Ext.ux.grid.filter.Filter} The existing or newly created filter object.
     */
    addFilter : function(config){
        var me = this, columns = me.getGridPanel().columns, i,
        columnsLength,
        column,
        filtersLength,
        filter;
        
        
        for(i = 0, columnsLength = columns.length; i < columnsLength; i ++ ){
            column = columns[i];
            if(column.dataIndex === config.dataIndex){
                column.filter = config;
            }
        }
        
        if(me.view.headerCt.menu){
            me.createFilters();
        }
        else{
            // Call getMenu() to ensure the menu is created, and so, also are the filters. We cannot call
            // createFilters() withouth having a menu because it will cause in a recursion to applyState()
            // that ends up to clear all the filter values. This is likely to happen when we reorder a column
            // and then add a new filter before the menu is recreated.
            me.view.headerCt.getMenu();
        }
        
        for(i = 0, filtersLength = me.filters.items.length; i < filtersLength; i ++ ){
            filter = me.filters.items[i];
            if(filter.dataIndex === config.dataIndex){
                return filter;
            }
        }
    },
    
    /**
     * Adds filters to the collection.
     * @param {Array} filters An Array of filter configuration objects.
     */
    addFilters : function(filters){
        if(filters){
            var me = this, i,
            filtersLength;
            for(i = 0, filtersLength = filters.length; i < filtersLength; i ++ ){
                me.addFilter(filters[i]);
            }
        }
    },
    
    /**
     * Returns a filter for the given dataIndex, if one exists.
     * @param {String} dataIndex The dataIndex of the desired filter object.
     * @return {Ext.ux.grid.filter.Filter}
     */
    getFilter : function(dataIndex){
        return this.filters.get(dataIndex);
    },
    
    /**
     * Turns all filters off. This does not clear the configuration information
     * (see {@link #removeAll}).
     */
    clearFilters : function(){
        this.filters.each(function(filter){
            filter.setActive(false);
        });
    },
    
    /**
     * Returns an Array of the currently active filters.
     * @return {Array} filters Array of the currently active filters.
     */
    getFilterData : function(){
        var filters = [], i,
        len;
        
        this.filters.each(function(f){
            if(f.active){
                var d = [].concat(f.serialize());
                for(i = 0, len = d.length; i < len; i ++ ){
                    filters.push({
                        field : f.dataIndex,
                        data : d[i]
                    });
                }
            }
        });
        return filters;
    },
    
    /**
     * Function to take the active filters data and build it into a query.
     * The format of the query depends on the {@link #encode} configuration:
     *
     *   - `false` (Default) :
     *     Flatten into query string of the form (assuming <code>{@link #paramPrefix}='filters'</code>:
     *
     *         filters[0][field]="someDataIndex"&
     *         filters[0][data][comparison]="someValue1"&
     *         filters[0][data][type]="someValue2"&
     *         filters[0][data][value]="someValue3"&
     *
     *
     *   - `true` :
     *     JSON encode the filter data
     *
     *         {filters:[{"field":"someDataIndex","comparison":"someValue1","type":"someValue2","value":"someValue3"}]}
     *
     * Override this method to customize the format of the filter query for remote requests.
     *
     * @param {Array} filters A collection of objects representing active filters and their configuration.
     * Each element will take the form of {field: dataIndex, data: filterConf}. dataIndex is not assured
     * to be unique as any one filter may be a composite of more basic filters for the same dataIndex.
     *
     * @return {Object} Query keys and values
     */
    buildQuery : function(filters){
        var p = {}, i,
        f,
        root,
        dataPrefix,
        key,
        tmp,
        len = filters.length;
        
        if( ! this.encode){
            for(i = 0; i < len; i ++ ){
                f = filters[i];
                root = [this.paramPrefix, '[', i, ']'].join('');
                p[root + '[field]'] = f.field;
                
                dataPrefix = root + '[data]';
                for(key in f.data){
                    p[[dataPrefix, '[', key, ']'].join('')] = f.data[key];
                }
            }
        }
        else{
            tmp = [];
            for(i = 0; i < len; i ++ ){
                f = filters[i];
                tmp.push(Ext.apply({}, 
                {
                    field : f.field
                }, f.data));
            }
            // only build if there is active filter
            if(tmp.length > 0){
                p[this.paramPrefix] = Ext.JSON.encode(tmp);
            }
        }
        return p;
    },
    
    /**
     * Removes filter related query parameters from the provided object.
     * @param {Object} p Query parameters that may contain filter related fields.
     */
    cleanParams : function(p){
        // if encoding just delete the property
        if(this.encode){
            delete p[this.paramPrefix];
            // otherwise scrub the object of filter data
        }
        else{
            var regex,
            key;
            regex = new RegExp('^' + this.paramPrefix + '\[[0-9]+\]');
            for(key in p){
                if(regex.test(key)){
                    delete p[key];
                }
            }
        }
    },
    
    /**
     * Function for locating filter classes, overwrite this with your favorite
     * loader to provide dynamic filter loading.
     * @param {String} type The type of filter to load ('Filter' is automatically
     * appended to the passed type; eg, 'string' becomes 'StringFilter').
     * @return {Function} The Ext.ux.grid.filter.Class
     */
    getFilterClass : function(type){
        // map the supported Ext.data.Field type values into a supported filter
        switch(type){
            case 'auto' : 
                type = 'string';
                break;
            case 'int' : 
            case 'float' : 
                type = 'numeric';
                break;
            case 'bool' : 
                type = 'boolean';
                break;
        }
        return Ext.ClassManager.getByAlias('gridfilter.' + type);
    }
});


/**
 * @class Ext.ux.grid.menu.RangeMenu
 * @extends Ext.menu.Menu
 * Custom implementation of {@link Ext.menu.Menu} that has preconfigured items for entering numeric
 * range comparison values: less-than, greater-than, and equal-to. This is used internally
 * by {@link Ext.ux.grid.filter.NumericFilter} to create its menu.
 */
Ext.define('Ext.ux.grid.menu.RangeMenu', 
{
    extend : 'Ext.menu.Menu',
    
    /**
     * @cfg {String} fieldCls
     * The Class to use to construct each field item within this menu
     * Defaults to:<pre>
     * fieldCls : Ext.form.field.Number
     * </pre>
     */
    fieldCls : 'Ext.form.field.Number',
    
    /**
     * @cfg {Object} fieldCfg
     * The default configuration options for any field item unless superseded
     * by the <code>{@link #fields}</code> configuration.
     * Defaults to:<pre>
     * fieldCfg : {}
     * </pre>
     * Example usage:
     * <pre><code>
fieldCfg : {
    width: 150,
},
     * </code></pre>
     */
    
    
    /**
     * @cfg {Object} fields
     * The field items may be configured individually
     * Defaults to <tt>undefined</tt>.
     * Example usage:
     * <pre><code>
fields : {
    gt: { // override fieldCfg options
        width: 200,
        fieldCls: Ext.ux.form.CustomNumberField // to override default {@link #fieldCls}
    }
},
     * </code></pre>
     */
    
    
    /**
     * @cfg {Object} itemIconCls
     * The itemIconCls to be applied to each comparator field item.
     * Defaults to:<pre>
itemIconCls : {
    gt : 'ux-rangemenu-gt',
    lt : 'ux-rangemenu-lt',
    eq : 'ux-rangemenu-eq'
}
     * </pre>
     */
    itemIconCls : 
    {
        gt : 'ux-rangemenu-gt',
        lt : 'ux-rangemenu-lt',
        eq : 'ux-rangemenu-eq'
    },
    
    /**
     * @cfg {Object} fieldLabels
     * Accessible label text for each comparator field item. Can be overridden by localization
     * files. Defaults to:<pre>
fieldLabels : {
     gt: 'Greater Than',
     lt: 'Less Than',
     eq: 'Equal To'
}</pre>
     */
    fieldLabels : 
    {
        gt : 'Greater Than',
        lt : 'Less Than',
        eq : 'Equal To'
    },
    
    /**
     * @cfg {Object} menuItemCfgs
     * Default configuration options for each menu item
     * Defaults to:<pre>
menuItemCfgs : {
    emptyText: 'Enter Filter Text...',
    selectOnFocus: true,
    width: 125
}
     * </pre>
     */
    menuItemCfgs : 
    {
        emptyText : 'Enter Number...',
        selectOnFocus : false,
        width : 155
    },
    
    /**
     * @cfg {Array} menuItems
     * The items to be shown in this menu.  Items are added to the menu
     * according to their position within this array. Defaults to:<pre>
     * menuItems : ['lt','gt','-','eq']
     * </pre>
     */
    menuItems : ['lt', 'gt', '-', 'eq'],
    
    
    constructor : function(config){
        var me = this, fields,
        fieldCfg,
        i,
        len,
        item,
        cfg,
        Cls;
        
        me.callParent(arguments);
        
        fields = me.fields = me.fields || {};
        fieldCfg = me.fieldCfg = me.fieldCfg || {};
        
        me.addEvents(
        /**
             * @event update
             * Fires when a filter configuration has changed
             * @param {Ext.ux.grid.filter.Filter} this The filter object.
             */
        'update');
        
        me.updateTask = Ext.create('Ext.util.DelayedTask', me.fireUpdate, me);
        
        for(i = 0, len = me.menuItems.length; i < len; i ++ ){
            item = me.menuItems[i];
            if(item !== '-'){
                // defaults
                cfg = 
                {
                    itemId : 'range-' + item,
                    enableKeyEvents : true,
                    hideLabel : false,
                    fieldLabel : me.iconTpl.apply({
                        cls : me.itemIconCls[item] || 'no-icon',
                        text : me.fieldLabels[item] || '',
                        src : Ext.BLANK_IMAGE_URL
                    }),
                    labelSeparator : '',
                    labelWidth : 29,
                    labelStyle : 'position: relative;',
                    listeners : 
                    {
                        scope : me,
                        change : me.onInputChange,
                        keyup : me.onInputKeyUp,
                        el : 
                        {
                            click : function(e){
                                e.stopPropagation();
                            }
                        }
                    },
                    activate : Ext.emptyFn,
                    deactivate : Ext.emptyFn
                };
                Ext.apply(cfg, 
                // custom configs
                Ext.applyIf(fields[item] || {}, fieldCfg[item]), 
                // configurable defaults
                me.menuItemCfgs);
                Cls = cfg.fieldCls || me.fieldCls;
                item = fields[item] = Ext.create(Cls, cfg);
            }
            me.add(item);
        }
    },
    
    /**
     * @private
     * called by this.updateTask
     */
    fireUpdate : function(){
        this.fireEvent('update', this);
    },
    
    /**
     * Get and return the value of the filter.
     * @return {String} The value of this filter
     */
    getValue : function(){
        var result = {}, key,
        field;
        for(key in this.fields){
            field = this.fields[key];
            if(field.isValid() && field.getValue() !== null){
                result[key] = field.getValue();
            }
        }
        return result;
    },
    
    /**
     * Set the value of this menu and fires the 'update' event.
     * @param {Object} data The data to assign to this menu
     */
    setValue : function(data){
        var me = this, key,
        field;
        
        for(key in me.fields){
            
            // Prevent field's change event from tiggering a Store filter. The final upate event will do that
            field = me.fields[key];
            field.suspendEvents();
            field.setValue(key in data ? data[key] : '');
            field.resumeEvents();
        }
        
        // Trigger the filering of the Store
        me.fireEvent('update', me);
    },
    
    /**  
     * @private
     * Handler method called when there is a keyup event on an input
     * item of this menu.
     */
    onInputKeyUp : function(field, e){
        if(e.getKey() === e.RETURN && field.isValid()){
            e.stopEvent();
            this.hide();
        }
    },
    
    /**
     * @private
     * Handler method called when the user changes the value of one of the input
     * items in this menu.
     */
    onInputChange : function(field){
        var me = this, fields = me.fields, eq = fields.eq, gt = fields.gt, lt = fields.lt;
        
        if(field == eq){
            if(gt){
                gt.setValue(null);
            }
            if(lt){
                lt.setValue(null);
            }
        }
        else{
            eq.setValue(null);
        }
        
        // restart the timer
        this.updateTask.delay(this.updateBuffer);
    }
}, function(){
    
    /**
     * @cfg {Ext.XTemplate} iconTpl
     * A template for generating the label for each field in the menu
     */
    this.prototype.iconTpl = Ext.create('Ext.XTemplate', '<img src="{src}" alt="{text}" class="' + Ext.baseCSSPrefix + 'menu-item-icon ux-rangemenu-icon {cls}" />');
    
});


/**
 * @class Ext.ux.grid.filter.Filter
 * @extends Ext.util.Observable
 * Abstract base class for filter implementations.
 */
Ext.define('Ext.ux.grid.filter.Filter', 
{
    extend : 'Ext.util.Observable',
    
    /**
     * @cfg {Boolean} active
     * Indicates the initial status of the filter (defaults to false).
     */
    active : false,
    /**
     * True if this filter is active.  Use setActive() to alter after configuration.
     * @type Boolean
     * @property active
     */
    
    /**
     * @cfg {String} dataIndex
     * The {@link Ext.data.Store} dataIndex of the field this filter represents.
     * The dataIndex does not actually have to exist in the store.
     */
    dataIndex : null,
    /**
     * The filter configuration menu that will be installed into the filter submenu of a column menu.
     * @type Ext.menu.Menu
     * @property
     */
    menu : null,
    /**
     * @cfg {Number} updateBuffer
     * Number of milliseconds to wait after user interaction to fire an update. Only supported
     * by filters: 'list', 'numeric', and 'string'. Defaults to 500.
     */
    updateBuffer : 500,
    
    constructor : function(config){
        Ext.apply(this, config);
        
        this.addEvents(
        /**
             * @event activate
             * Fires when an inactive filter becomes active
             * @param {Ext.ux.grid.filter.Filter} this
             */
        'activate', 
        /**
             * @event deactivate
             * Fires when an active filter becomes inactive
             * @param {Ext.ux.grid.filter.Filter} this
             */
        'deactivate', 
        /**
             * @event serialize
             * Fires after the serialization process. Use this to attach additional parameters to serialization
             * data before it is encoded and sent to the server.
             * @param {Array/Object} data A map or collection of maps representing the current filter configuration.
             * @param {Ext.ux.grid.filter.Filter} filter The filter being serialized.
             */
        'serialize', 
        /**
             * @event update
             * Fires when a filter configuration has changed
             * @param {Ext.ux.grid.filter.Filter} this The filter object.
             */
        'update');
        Ext.ux.grid.filter.Filter.superclass.constructor.call(this);
        
        this.menu = this.createMenu(config);
        this.init(config);
        if(config && config.value){
            this.setValue(config.value);
            this.setActive(config.active !== false, true);
            delete config.value;
        }
    },
    
    /**
     * Destroys this filter by purging any event listeners, and removing any menus.
     */
    destroy : function(){
        if(this.menu){
            this.menu.destroy();
        }
        this.clearListeners();
    },
    
    /**
     * Template method to be implemented by all subclasses that is to
     * initialize the filter and install required menu items.
     * Defaults to Ext.emptyFn.
     */
    init : Ext.emptyFn,
    
    /**
     * @private @override
     * Creates the Menu for this filter.
     * @param {Object} config Filter configuration
     * @return {Ext.menu.Menu}
     */
    createMenu : function(config){
        return Ext.create('Ext.menu.Menu', config);
    },
    
    /**
     * Template method to be implemented by all subclasses that is to
     * get and return the value of the filter.
     * Defaults to Ext.emptyFn.
     * @return {Object} The 'serialized' form of this filter
     * @methodOf Ext.ux.grid.filter.Filter
     */
    getValue : Ext.emptyFn,
    
    /**
     * Template method to be implemented by all subclasses that is to
     * set the value of the filter and fire the 'update' event.
     * Defaults to Ext.emptyFn.
     * @param {Object} data The value to set the filter
     * @methodOf Ext.ux.grid.filter.Filter
     */
    setValue : Ext.emptyFn,
    
    /**
     * Template method to be implemented by all subclasses that is to
     * return <tt>true</tt> if the filter has enough configuration information to be activated.
     * Defaults to <tt>return true</tt>.
     * @return {Boolean}
     */
    isActivatable : function(){
        return true;
    },
    
    /**
     * Template method to be implemented by all subclasses that is to
     * get and return serialized filter data for transmission to the server.
     * Defaults to Ext.emptyFn.
     */
    getSerialArgs : Ext.emptyFn,
    
    /**
     * Template method to be implemented by all subclasses that is to
     * validates the provided Ext.data.Record against the filters configuration.
     * Defaults to <tt>return true</tt>.
     * @param {Ext.data.Record} record The record to validate
     * @return {Boolean} true if the record is valid within the bounds
     * of the filter, false otherwise.
     */
    validateRecord : function(){
        return true;
    },
    
    /**
     * Returns the serialized filter data for transmission to the server
     * and fires the 'serialize' event.
     * @return {Object/Array} An object or collection of objects containing
     * key value pairs representing the current configuration of the filter.
     * @methodOf Ext.ux.grid.filter.Filter
     */
    serialize : function(){
        var args = this.getSerialArgs();
        this.fireEvent('serialize', args, this);
        return args;
    },
    
    /** @private */
    fireUpdate : function(){
        if(this.active){
            this.fireEvent('update', this);
        }
        this.setActive(this.isActivatable());
    },
    
    /**
     * Sets the status of the filter and fires the appropriate events.
     * @param {Boolean} active        The new filter state.
     * @param {Boolean} suppressEvent True to prevent events from being fired.
     * @methodOf Ext.ux.grid.filter.Filter
     */
    setActive : function(active, suppressEvent){
        if(this.active != active){
            this.active = active;
            if(suppressEvent !== true){
                this.fireEvent(active ? 'activate' : 'deactivate', this);
            }
        }
    }
});


/**
 * @class Ext.ux.grid.filter.BooleanFilter
 * @extends Ext.ux.grid.filter.Filter
 * Boolean filters use unique radio group IDs (so you can have more than one!)
 * <p><b><u>Example Usage:</u></b></p>
 * <pre><code>
var filters = Ext.create('Ext.ux.grid.GridFilters', {
    ...
    filters: [{
        // required configs
        type: 'boolean',
        dataIndex: 'visible'

        // optional configs
        defaultValue: null, // leave unselected (false selected by default)
        yesText: 'Yes',     // default
        noText: 'No'        // default
    }]
});
 * </code></pre>
 */
Ext.define('Ext.ux.grid.filter.BooleanFilter', 
{
    extend : 'Ext.ux.grid.filter.Filter',
    alias : 'gridfilter.boolean',
    
    /**
	 * @cfg {Boolean} defaultValue
	 * Set this to null if you do not want either option to be checked by default. Defaults to false.
	 */
    defaultValue : false,
    /**
	 * @cfg {String} yesText
	 * Defaults to 'Yes'.
	 */
    yesText : 'Yes',
    /**
	 * @cfg {String} noText
	 * Defaults to 'No'.
	 */
    noText : 'No',
    
    /**
     * @private
     * Template method that is to initialize the filter and install required menu items.
     */
    init : function(config){
        var gId = Ext.id();
        this.options = [Ext.create('Ext.menu.CheckItem', 
        {
            text : this.yesText,
            group : gId,
            checked : this.defaultValue === true
        }), Ext.create('Ext.menu.CheckItem', 
        {
            text : this.noText,
            group : gId,
            checked : this.defaultValue === false
        })];
        
        this.menu.add(this.options[0], this.options[1]);
        
        for(var i = 0; i < this.options.length; i ++ ){
            this.options[i].on('click', this.fireUpdate, this);
            this.options[i].on('checkchange', this.fireUpdate, this);
        }
    },
    
    /**
     * @private
     * Template method that is to get and return the value of the filter.
     * @return {String} The value of this filter
     */
    getValue : function(){
        return this.options[0].checked;
    },
    
    /**
     * @private
     * Template method that is to set the value of the filter.
     * @param {Object} value The value to set the filter
     */
    setValue : function(value){
        this.options[value ? 0 : 1].setChecked(true);
    },
    
    /**
     * @private
     * Template method that is to get and return serialized filter data for
     * transmission to the server.
     * @return {Object/Array} An object or collection of objects containing
     * key value pairs representing the current configuration of the filter.
     */
    getSerialArgs : function(){
        var args = 
        {
            type : 'boolean', value : this.getValue()
        };
        return args;
    },
    
    /**
     * Template method that is to validate the provided Ext.data.Record
     * against the filters configuration.
     * @param {Ext.data.Record} record The record to validate
     * @return {Boolean} true if the record is valid within the bounds
     * of the filter, false otherwise.
     */
    validateRecord : function(record){
        return record.get(this.dataIndex) == this.getValue();
    }
});



/**
 * @class Ext.ux.grid.filter.StringFilter
 * @extends Ext.ux.grid.filter.Filter
 * Filter by a configurable Ext.form.field.Text
 * <p><b><u>Example Usage:</u></b></p>
 * <pre><code>
var filters = Ext.create('Ext.ux.grid.GridFilters', {
    ...
    filters: [{
        // required configs
        type: 'string',
        dataIndex: 'name',

        // optional configs
        value: 'foo',
        active: true, // default is false
        iconCls: 'ux-gridfilter-text-icon' // default
        // any Ext.form.field.Text configs accepted
    }]
});
 * </code></pre>
 */
Ext.define('Ext.ux.grid.filter.StringFilter', 
{
    extend : 'Ext.ux.grid.filter.Filter',
    alias : 'gridfilter.string',
    
    /**
     * @cfg {String} iconCls
     * The iconCls to be applied to the menu item.
     * Defaults to <tt>'ux-gridfilter-text-icon'</tt>.
     */
    iconCls : 'ux-gridfilter-text-icon',
    
    emptyText : 'Enter Filter Text...',
    selectOnFocus : true,
    width : 125,
    
    /**
     * @private
     * Template method that is to initialize the filter and install required menu items.
     */
    init : function(config){
        Ext.applyIf(config, 
        {
            enableKeyEvents : true,
            iconCls : this.iconCls,
            hideLabel : true,
            listeners : 
            {
                scope : this,
                keyup : this.onInputKeyUp,
                el : 
                {
                    click : function(e){
                        e.stopPropagation();
                    }
                }
            }
        });
        
        this.inputItem = Ext.create('Ext.form.field.Text', config);
        this.menu.add(this.inputItem);
        this.updateTask = Ext.create('Ext.util.DelayedTask', this.fireUpdate, this);
    },
    
    /**
     * @private
     * Template method that is to get and return the value of the filter.
     * @return {String} The value of this filter
     */
    getValue : function(){
        return this.inputItem.getValue();
    },
    
    /**
     * @private
     * Template method that is to set the value of the filter.
     * @param {Object} value The value to set the filter
     */
    setValue : function(value){
        this.inputItem.setValue(value);
        this.fireEvent('update', this);
    },
    
    /**
     * @private
     * Template method that is to return <tt>true</tt> if the filter
     * has enough configuration information to be activated.
     * @return {Boolean}
     */
    isActivatable : function(){
        return this.inputItem.getValue().length > 0;
    },
    
    /**
     * @private
     * Template method that is to get and return serialized filter data for
     * transmission to the server.
     * @return {Object/Array} An object or collection of objects containing
     * key value pairs representing the current configuration of the filter.
     */
    getSerialArgs : function(){
        return {
            type : 'string',
            value : this.getValue()
        };
    },
    
    /**
     * Template method that is to validate the provided Ext.data.Record
     * against the filters configuration.
     * @param {Ext.data.Record} record The record to validate
     * @return {Boolean} true if the record is valid within the bounds
     * of the filter, false otherwise.
     */
    validateRecord : function(record){
        var val = record.get(this.dataIndex);
        
        if(typeof val != 'string'){
            return(this.getValue().length === 0);
        }
        
        return val.toLowerCase().indexOf(this.getValue().toLowerCase()) > -1;
    },
    
    /**
     * @private
     * Handler method called when there is a keyup event on this.inputItem
     */
    onInputKeyUp : function(field, e){
        var k = e.getKey();
        if(k == e.RETURN && field.isValid()){
            e.stopEvent();
            this.menu.hide();
            return;
        }
        // restart the timer
        this.updateTask.delay(this.updateBuffer);
    }
});


/**
 * @class Ext.ux.grid.filter.NumericFilter
 * @extends Ext.ux.grid.filter.Filter
 * Filters using an Ext.ux.grid.menu.RangeMenu.
 * <p><b><u>Example Usage:</u></b></p>
 * <pre><code>
var filters = Ext.create('Ext.ux.grid.GridFilters', {
    ...
    filters: [{
        type: 'numeric',
        dataIndex: 'price'
    }]
});
 * </code></pre>
 * <p>Any of the configuration options for {@link Ext.ux.grid.menu.RangeMenu} can also be specified as
 * configurations to NumericFilter, and will be copied over to the internal menu instance automatically.</p>
 */
Ext.define('Ext.ux.grid.filter.NumericFilter', 
{
    extend : 'Ext.ux.grid.filter.Filter',
    alias : 'gridfilter.numeric',
    uses : ['Ext.form.field.Number'],
    
    /**
     * @private @override
     * Creates the Menu for this filter.
     * @param {Object} config Filter configuration
     * @return {Ext.menu.Menu}
     */
    createMenu : function(config){
        var me = this, menu;
        menu = Ext.create('Ext.ux.grid.menu.RangeMenu', config);
        menu.on('update', me.fireUpdate, me);
        return menu;
    },
    
    /**
     * @private
     * Template method that is to get and return the value of the filter.
     * @return {String} The value of this filter
     */
    getValue : function(){
        return this.menu.getValue();
    },
    
    /**
     * @private
     * Template method that is to set the value of the filter.
     * @param {Object} value The value to set the filter
     */
    setValue : function(value){
        this.menu.setValue(value);
    },
    
    /**
     * @private
     * Template method that is to return <tt>true</tt> if the filter
     * has enough configuration information to be activated.
     * @return {Boolean}
     */
    isActivatable : function(){
        var values = this.getValue(), key;
        for(key in values){
            if(values[key] !== undefined){
                return true;
            }
        }
        return false;
    },
    
    /**
     * @private
     * Template method that is to get and return serialized filter data for
     * transmission to the server.
     * @return {Object/Array} An object or collection of objects containing
     * key value pairs representing the current configuration of the filter.
     */
    getSerialArgs : function(){
        var key,
        args = [], values = this.menu.getValue();
        for(key in values){
            args.push({
                type : 'numeric',
                comparison : key,
                value : values[key]
            });
        }
        return args;
    },
    
    /**
     * Template method that is to validate the provided Ext.data.Record
     * against the filters configuration.
     * @param {Ext.data.Record} record The record to validate
     * @return {Boolean} true if the record is valid within the bounds
     * of the filter, false otherwise.
     */
    validateRecord : function(record){
        var val = record.get(this.dataIndex), values = this.getValue(), isNumber = Ext.isNumber;
        if(isNumber(values.eq) && val != values.eq){
            return false;
        }
        if(isNumber(values.lt) && val >= values.lt){
            return false;
        }
        if(isNumber(values.gt) && val <= values.gt){
            return false;
        }
        return true;
    }
});


/**
 * @class Ext.ux.grid.filter.ListFilter
 * @extends Ext.ux.grid.filter.Filter
 * <p>List filters are able to be preloaded/backed by an Ext.data.Store to load
 * their options the first time they are shown. ListFilter utilizes the
 * {@link Ext.ux.grid.menu.ListMenu} component.</p>
 * <p>Although not shown here, this class accepts all configuration options
 * for {@link Ext.ux.grid.menu.ListMenu}.</p>
 *
 * <p><b><u>Example Usage:</u></b></p>
 * <pre><code>
var filters = Ext.create('Ext.ux.grid.GridFilters', {
    ...
    filters: [{
        type: 'list',
        dataIndex: 'size',
        phpMode: true,
        // options will be used as data to implicitly creates an ArrayStore
        options: ['extra small', 'small', 'medium', 'large', 'extra large']
    }]
});
 * </code></pre>
 *
 */
Ext.define('Ext.ux.grid.filter.ListFilter', 
{
    extend : 'Ext.ux.grid.filter.Filter',
    alias : 'gridfilter.list',
    
    /**
     * @cfg {Array} options
     * <p><code>data</code> to be used to implicitly create a data store
     * to back this list when the data source is <b>local</b>. If the
     * data for the list is remote, use the <code>{@link #store}</code>
     * config instead.</p>
     * <br><p>Each item within the provided array may be in one of the
     * following formats:</p>
     * <div class="mdetail-params"><ul>
     * <li><b>Array</b> :
     * <pre><code>
options: [
    [11, 'extra small'],
    [18, 'small'],
    [22, 'medium'],
    [35, 'large'],
    [44, 'extra large']
]
     * </code></pre>
     * </li>
     * <li><b>Object</b> :
     * <pre><code>
labelField: 'name', // override default of 'text'
options: [
    {id: 11, name:'extra small'},
    {id: 18, name:'small'},
    {id: 22, name:'medium'},
    {id: 35, name:'large'},
    {id: 44, name:'extra large'}
]
     * </code></pre>
     * </li>
     * <li><b>String</b> :
     * <pre><code>
     * options: ['extra small', 'small', 'medium', 'large', 'extra large']
     * </code></pre>
     * </li>
     */
    
    /**
     * @cfg {Boolean} phpMode
     * <p>Adjust the format of this filter. Defaults to false.</p>
     * <br><p>When GridFilters <code>@cfg encode = false</code> (default):</p>
     * <pre><code>
// phpMode == false (default):
filter[0][data][type] list
filter[0][data][value] value1
filter[0][data][value] value2
filter[0][field] prod

// phpMode == true:
filter[0][data][type] list
filter[0][data][value] value1, value2
filter[0][field] prod
     * </code></pre>
     * When GridFilters <code>@cfg encode = true</code>:
     * <pre><code>
// phpMode == false (default):
filter : [{"type":"list","value":["small","medium"],"field":"size"}]

// phpMode == true:
filter : [{"type":"list","value":"small,medium","field":"size"}]
     * </code></pre>
     */
    phpMode : false,
    /**
     * @cfg {Ext.data.Store} store
     * The {@link Ext.data.Store} this list should use as its data source
     * when the data source is <b>remote</b>. If the data for the list
     * is local, use the <code>{@link #options}</code> config instead.
     */
    
    
    /**
     * @private
     * Template method that is to initialize the filter.
     * @param {Object} config
     */
    init : function(config){
        this.dt = Ext.create('Ext.util.DelayedTask', this.fireUpdate, this);
    },
    
    /**
     * @private @override
     * Creates the Menu for this filter.
     * @param {Object} config Filter configuration
     * @return {Ext.menu.Menu}
     */
    createMenu : function(config){
        var menu = Ext.create('Ext.ux.grid.menu.ListMenu', config);
        menu.on('checkchange', this.onCheckChange, this);
        return menu;
    },
    
    /**
     * @private
     * Template method that is to get and return the value of the filter.
     * @return {String} The value of this filter
     */
    getValue : function(){
        return this.menu.getSelected();
    },
    /**
     * @private
     * Template method that is to set the value of the filter.
     * @param {Object} value The value to set the filter
     */
    setValue : function(value){
        this.menu.setSelected(value);
        this.fireEvent('update', this);
    },
    
    /**
     * @private
     * Template method that is to return <tt>true</tt> if the filter
     * has enough configuration information to be activated.
     * @return {Boolean}
     */
    isActivatable : function(){
        return this.getValue().length > 0;
    },
    
    /**
     * @private
     * Template method that is to get and return serialized filter data for
     * transmission to the server.
     * @return {Object/Array} An object or collection of objects containing
     * key value pairs representing the current configuration of the filter.
     */
    getSerialArgs : function(){
        return {
            type : 'list',
            value : this.phpMode ? this.getValue().join(',') : this.getValue()
        };
    },
    
    /** @private */
    onCheckChange : function(){
        this.dt.delay(this.updateBuffer);
    },
    
    
    /**
     * Template method that is to validate the provided Ext.data.Record
     * against the filters configuration.
     * @param {Ext.data.Record} record The record to validate
     * @return {Boolean} true if the record is valid within the bounds
     * of the filter, false otherwise.
     */
    validateRecord : function(record){
        var valuesArray = this.getValue();
        return Ext.Array.indexOf(valuesArray, record.get(this.dataIndex)) > -1;
    }
});


/**
 * @class Ext.ux.grid.filter.DateFilter
 * @extends Ext.ux.grid.filter.Filter
 * Filter by a configurable Ext.picker.DatePicker menu
 * <p><b><u>Example Usage:</u></b></p>
 * <pre><code>
var filters = Ext.create('Ext.ux.grid.GridFilters', {
    ...
    filters: [{
        // required configs
        type: 'date',
        dataIndex: 'dateAdded',

        // optional configs
        dateFormat: 'm/d/Y',  // default
        beforeText: 'Before', // default
        afterText: 'After',   // default
        onText: 'On',         // default
        pickerOpts: {
            // any DatePicker configs
        },

        active: true // default is false
    }]
});
 * </code></pre>
 */
Ext.define('Ext.ux.grid.filter.DateFilter', 
{
    extend : 'Ext.ux.grid.filter.Filter',
    alias : 'gridfilter.date',
    uses : ['Ext.picker.Date', 'Ext.menu.Menu'],
    
    /**
     * @cfg {String} afterText
     * Defaults to 'After'.
     */
    afterText : 'After',
    /**
     * @cfg {String} beforeText
     * Defaults to 'Before'.
     */
    beforeText : 'Before',
    /**
     * @cfg {Object} compareMap
     * Map for assigning the comparison values used in serialization.
     */
    compareMap : 
    {
        before : 'lt',
        after : 'gt',
        on : 'eq'
    },
    /**
     * @cfg {String} dateFormat
     * The date format to return when using getValue.
     * Defaults to 'm/d/Y'.
     */
    dateFormat : 'm/d/Y',
    
    /**
     * @cfg {Date} maxDate
     * Allowable date as passed to the Ext.DatePicker
     * Defaults to undefined.
     */
    
    /**
     * @cfg {Date} minDate
     * Allowable date as passed to the Ext.DatePicker
     * Defaults to undefined.
     */
    
    /**
     * @cfg {Array} menuItems
     * The items to be shown in this menu
     * Defaults to:<pre>
     * menuItems : ['before', 'after', '-', 'on'],
     * </pre>
     */
    menuItems : ['before', 'after', '-', 'on'],
    
    /**
     * @cfg {Object} menuItemCfgs
     * Default configuration options for each menu item
     */
    menuItemCfgs : 
    {
        selectOnFocus : true,
        width : 125
    },
    
    /**
     * @cfg {String} onText
     * Defaults to 'On'.
     */
    onText : 'On',
    
    /**
     * @cfg {Object} pickerOpts
     * Configuration options for the date picker associated with each field.
     */
    pickerOpts : {},
    
    /**
     * @private
     * Template method that is to initialize the filter and install required menu items.
     */
    init : function(config){
        var me = this, pickerCfg,
        i,
        len,
        item,
        cfg;
        
        pickerCfg = Ext.apply(me.pickerOpts, 
        {
            xtype : 'datepicker',
            minDate : me.minDate,
            maxDate : me.maxDate,
            format : me.dateFormat,
            listeners : 
            {
                scope : me,
                select : me.onMenuSelect
            }
        });
        
        me.fields = {};
        for(i = 0, len = me.menuItems.length; i < len; i ++ ){
            item = me.menuItems[i];
            if(item !== '-'){
                cfg = 
                {
                    itemId : 'range-' + item,
                    text : me[item + 'Text'],
                    menu : Ext.create('Ext.menu.Menu', 
                    {
                        items : [Ext.apply(pickerCfg, 
                        {
                            itemId : item,
                            listeners : 
                            {
                                select : me.onPickerSelect,
                                scope : me
                            }
                        }), ]
                    }),
                    listeners : 
                    {
                        scope : me,
                        checkchange : me.onCheckChange
                    }
                };
                item = me.fields[item] = Ext.create('Ext.menu.CheckItem', cfg);
            }
            //me.add(item);
            me.menu.add(item);
        }
        me.values = {};
    },
    
    onCheckChange : function(item, checked){
        var me = this, picker = item.menu.items.first(), itemId = picker.itemId, values = me.values;
        
        if(checked){
            values[itemId] = picker.getValue();
        }
        else{
            delete values[itemId]
        }
        me.setActive(me.isActivatable());
        me.fireEvent('update', me);
    },
    
    /**
     * @private
     * Handler method called when there is a keyup event on an input
     * item of this menu.
     */
    onInputKeyUp : function(field, e){
        var k = e.getKey();
        if(k == e.RETURN && field.isValid()){
            e.stopEvent();
            this.menu.hide();
        }
    },
    
    /**
     * Handler for when the DatePicker for a field fires the 'select' event
     * @param {Ext.picker.Date} picker
     * @param {Object} date
     */
    onMenuSelect : function(picker, date){
        var fields = this.fields, field = this.fields[picker.itemId];
        
        field.setChecked(true);
        
        if(field == fields.on){
            fields.before.setChecked(false, true);
            fields.after.setChecked(false, true);
        }
        else{
            fields.on.setChecked(false, true);
            if(field == fields.after && this.getFieldValue('before') < date){
                fields.before.setChecked(false, true);
            }
            else if(field == fields.before && this.getFieldValue('after') > date){
                fields.after.setChecked(false, true);
            }
        }
        this.fireEvent('update', this);
        
        picker.up('menu').hide();
    },
    
    /**
     * @private
     * Template method that is to get and return the value of the filter.
     * @return {String} The value of this filter
     */
    getValue : function(){
        var key,
        result = {};
        for(key in this.fields){
            if(this.fields[key].checked){
                result[key] = this.getFieldValue(key);
            }
        }
        return result;
    },
    
    /**
     * @private
     * Template method that is to set the value of the filter.
     * @param {Object} value The value to set the filter
     * @param {Boolean} preserve true to preserve the checked status
     * of the other fields.  Defaults to false, unchecking the
     * other fields
     */
    setValue : function(value, preserve){
        var key;
        for(key in this.fields){
            if(value[key]){
                this.getPicker(key).setValue(value[key]);
                this.fields[key].setChecked(true);
            }
            else if( ! preserve){
                this.fields[key].setChecked(false);
            }
        }
        this.fireEvent('update', this);
    },
    
    /**
     * @private
     * Template method that is to return <tt>true</tt> if the filter
     * has enough configuration information to be activated.
     * @return {Boolean}
     */
    isActivatable : function(){
        var key;
        for(key in this.fields){
            if(this.fields[key].checked){
                return true;
            }
        }
        return false;
    },
    
    /**
     * @private
     * Template method that is to get and return serialized filter data for
     * transmission to the server.
     * @return {Object/Array} An object or collection of objects containing
     * key value pairs representing the current configuration of the filter.
     */
    getSerialArgs : function(){
        var args = [];
        for(var key in this.fields){
            if(this.fields[key].checked){
                args.push({
                    type : 'date',
                    comparison : this.compareMap[key],
                    value : Ext.Date.format(this.getFieldValue(key), this.dateFormat)
                });
            }
        }
        return args;
    },
    
    /**
     * Get and return the date menu picker value
     * @param {String} item The field identifier ('before', 'after', 'on')
     * @return {Date} Gets the current selected value of the date field
     */
    getFieldValue : function(item){
        return this.values[item];
    },
    
    /**
     * Gets the menu picker associated with the passed field
     * @param {String} item The field identifier ('before', 'after', 'on')
     * @return {Object} The menu picker
     */
    getPicker : function(item){
        return this.fields[item].menu.items.first();
    },
    
    /**
     * Template method that is to validate the provided Ext.data.Record
     * against the filters configuration.
     * @param {Ext.data.Record} record The record to validate
     * @return {Boolean} true if the record is valid within the bounds
     * of the filter, false otherwise.
     */
    validateRecord : function(record){
        var key,
        pickerValue,
        val = record.get(this.dataIndex), clearTime = Ext.Date.clearTime;
        
        if( ! Ext.isDate(val)){
            return false;
        }
        val = clearTime(val, true).getTime();
        
        for(key in this.fields){
            if(this.fields[key].checked){
                pickerValue = clearTime(this.getFieldValue(key), true).getTime();
                if(key == 'before' && pickerValue <= val){
                    return false;
                }
                if(key == 'after' && pickerValue >= val){
                    return false;
                }
                if(key == 'on' && pickerValue != val){
                    return false;
                }
            }
        }
        return true;
    },
    
    onPickerSelect : function(picker, date){
        // keep track of the picker value separately because the menu gets destroyed
        // when columns order changes.  We return this value from getValue() instead
        // of picker.getValue()
        this.values[picker.itemId] = date;
        this.fireEvent('update', this);
    }
});



/**
* @class Ext.ux.grid.menu.ListMenu
* @extends Ext.menu.Menu
* This is a supporting class for {@link Ext.ux.grid.filter.ListFilter}.
* Although not listed as configuration options for this class, this class
* also accepts all configuration options from {@link Ext.ux.grid.filter.ListFilter}.
*/
Ext.define('Ext.ux.grid.menu.ListMenu', 
{
    extend : 'Ext.menu.Menu',
    
    /**
    * @cfg {String} labelField
    * Defaults to 'text'.
    */
    labelField: 'text',
    
    valueField: 'id',
    /**
    * @cfg {String} paramPrefix
    * Defaults to 'Loading...'.
    */
    loadingText : 'Loading...',
    /**
    * @cfg {Boolean} loadOnShow
    * Defaults to true.
    */
    loadOnShow : true,
    /**
    * @cfg {Boolean} single
    * Specify true to group all items in this list into a single-select
    * radio button group. Defaults to false.
    */
    single : false,
    
    constructor : function(cfg){
        var me = this, options,
        i,
        len,
        value;
        
        me.selected = [];
        me.addEvents(
        /**
        * @event checkchange
        * Fires when there is a change in checked items from this list
        * @param {Object} item Ext.menu.CheckItem
        * @param {Object} checked The checked value that was set
        */
        'checkchange');
        
        me.callParent([cfg = cfg || {}]);
        
        if( ! cfg.store && cfg.options){
            options = [];
            for(i = 0, len = cfg.options.length; i < len; i ++ ){
                value = cfg.options[i];
                switch(Ext.type(value)){
                    case 'array' : 
                        options.push(value);
                        break;
                    case 'object' : 
                        options.push([value.id, value[me.labelField]]);
                        break;
                    case 'string' : 
                        options.push([value, value]);
                        break;
                }
            }
            
            me.store = Ext.create('Ext.data.ArrayStore', 
            {
                fields : ['id', me.labelField],
                data : options,
                listeners : 
                {
                    load : me.onLoad,
                    scope : me
                }
            });
            me.loaded = true;
            me.autoStore = true;
        }
        else{
            me.add({
                text : me.loadingText,
                iconCls : 'loading-indicator'
            });
            me.store.on('load', me.onLoad, me);
        }
    },
    
    destroy : function(){
        var me = this, store = me.store;
        
        if(store){
            if(me.autoStore){
                store.destroyStore();
            }
            else{
                store.un('unload', me.onLoad, me);
            }
        }
        me.callParent();
    },
    
    /**
    * Lists will initially show a 'loading' item while the data is retrieved from the store.
    * In some cases the loaded data will result in a list that goes off the screen to the
    * right (as placement calculations were done with the loading item). This adapter will
    * allow show to be called with no arguments to show with the previous arguments and
    * thus recalculate the width and potentially hang the menu from the left.
    */
    show : function(){
        if(this.loadOnShow && !this.loaded && !this.store.loading){
            this.store.load();
        }
        this.callParent();
    },
    
    /** @private */
    onLoad : function(store, records){
        var me = this, gid,
        itemValue,
        i,
        len,
        listeners = 
        {
            checkchange : me.checkChange,
            scope : me
        };
        
        Ext.suspendLayouts();
        me.removeAll(true);
        
        gid = me.single ? Ext.id() : null;
        for(i = 0, len = records.length; i < len; i ++ ){
            itemValue = records[i].get(me.valueField);
            me.add(Ext.create('Ext.menu.CheckItem', 
            {
                text : records[i].get(me.labelField),
                group : gid,
                checked : Ext.Array.contains(me.selected, itemValue),
                hideOnClick : false,
                value : itemValue,
                listeners : listeners
            }));
        }
        
        me.loaded = true;
        Ext.resumeLayouts(true);
        me.fireEvent('load', me, records);
    },
    
    /**
    * Get the selected items.
    * @return {Array} selected
    */
    getSelected : function(){
        return this.selected;
    },
    
    /** @private */
    setSelected : function(value){
        value = this.selected = [].concat(value);
        
        if(this.loaded){
            this.items.each(function(item){
                item.setChecked(false, true);
                for(var i = 0, len = value.length; i < len; i ++ ){
                    if(item.value == value[i]){
                        item.setChecked(true, true);
                    }
                }
            }, this);
        }
    },
    
    /**
    * Handler for the 'checkchange' event from an check item in this menu
    * @param {Object} item Ext.menu.CheckItem
    * @param {Object} checked The checked value that was set
    */
    checkChange : function(item, checked){
        var value = [];
        this.items.each(function(item){
            if(item.checked){
                value.push(item.value);
            }
        }, this);
        this.selected = value;
        
        this.fireEvent('checkchange', item, checked);
    }
});